package com.wkbb.basic.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wkbb.basic.feign.DoorFeignService;
import com.wkbb.basic.service.DeviceBoxService;
import com.wkbb.basic.service.JvtBoxService;
import com.wkbb.basic.utils.jvt.JvtBoxBean;
import com.wkbb.basic.utils.jvt.JvtBoxConfig;
import com.wkbb.basic.utils.jvt.JvtBoxUtil;
import com.wkbb.common.constants.RedisKeyConstant;
import com.wkbb.common.dto.basic.DeviceBoxDto;
import com.wkbb.common.dto.door.DoorDeviceLogsDto;
import com.wkbb.common.redis.RedisTemplateUtil;
import com.wkbb.common.utils.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @author Alger Yang
 * @date 2019/9/25 002513:46
 */
@Service
@Slf4j
public class JvtBoxServiceImpl implements JvtBoxService {

    /**
     * 巨龙盒子配置
     */
    @Autowired
    private JvtBoxConfig jvtBoxConfig;

    @Autowired
    private RedisTemplateUtil redisTemplateUtil;

    @Autowired
    private DoorFeignService doorFeignService;

    @Autowired
    private DeviceBoxService deviceBoxService;

    private ScheduledExecutorService executorService;

    /**
     * 上一次任务状态刷新时间
     */
    private long mLastTaskTime = 0;
    /**
     * 上一次执行的任务
     */
    private volatile JvtBoxBean mLastTask = null;
    /**
     * 30秒未刷新任务状态，重启线程池
     */
    private static final long TASK_TIME_OUT = 30 * 1000;


    @Override
    public JvtBoxBean getJvtBoxInfo(String uuid) {
        return JvtBoxUtil.getDeviceInfo(uuid);
    }

    @Override
    public JvtBoxBean syncJvtTime(String uuid) {
        return JvtBoxUtil.syncTime(uuid);
    }

    @Override
    public JvtBoxBean setJvtParams(String uuid) {
        return JvtBoxUtil.setParams(uuid, jvtBoxConfig);
    }

    @Override
    public JvtBoxBean getJvtParams(String uuid) {
        return JvtBoxUtil.getParams(uuid);
    }

    @Override
    public JvtBoxBean getJvtPersonList(String uuid, Integer personType) {
        return JvtBoxUtil.getPersonList(uuid, personType);
    }

    @Override
    public JvtBoxBean getJvtPerson(String uuid, Integer personType, String personId, Boolean getPhoto) {
        return JvtBoxUtil.getPerson(uuid, personType, personId, getPhoto);
    }

    @Override
    public JvtBoxBean addJvtPerson(String uuid, Integer personType, String personId, String image) {
        return JvtBoxUtil.addPerson(uuid, personType, personId, image);
    }

    @Override
    public void pushAddJvtPersonTask(String uuid, Integer personType, String personId, String faceUrl) {
        log.info("【巨龙】添加人员（加入队列） UUID:{},personId:{}", uuid, personId);

        JvtBoxBean task = JvtBoxUtil.getAddPersonTask(uuid, personType, personId, faceUrl);
        redisTemplateUtil.leftPush(getTaskKey(uuid), task);
        //将每个设备的任务队列 的key 放到 set里面，不再使用scan命令去模糊查询设备的任务队列key（性能很差）
        redisTemplateUtil.sAdd(RedisKeyConstant.WKBB_BASIC_JVT_TASK_KEYS,getTaskKey(uuid));
    }

    @Override
    public void pushDeleteJvtPersonTask(String uuid, Integer personType, String personId) {
        log.info("【巨龙】删除人员（加入队列） UUID:{},personType:{},personId:{}", uuid, personType, personId);

        JvtBoxBean task = JvtBoxUtil.getDeletePersonTask(uuid, personType, personId);
        redisTemplateUtil.leftPush(getTaskKey(uuid), task);
        //将每个设备的任务队列 的key 放到 set里面，不再使用scan命令去模糊查询设备的任务队列key（性能很差）
        redisTemplateUtil.sAdd(RedisKeyConstant.WKBB_BASIC_JVT_TASK_KEYS,getTaskKey(uuid));
    }

    @Override
    public void pushDeleteJvtPersonListTask(String uuid, Integer personType) {
        log.info("【巨龙】删除人员名单（加入队列） UUID:{},personType:{}", uuid, personType);

        JvtBoxBean task = JvtBoxUtil.getDeletePersonListTask(uuid, personType);
        redisTemplateUtil.leftPush(getTaskKey(uuid), task);
        //将每个设备的任务队列 的key 放到 set里面，不再使用scan命令去模糊查询设备的任务队列key（性能很差）
        redisTemplateUtil.sAdd(RedisKeyConstant.WKBB_BASIC_JVT_TASK_KEYS,getTaskKey(uuid));
    }

    @Override
    public JvtBoxBean deleteJvtPersonList(String uuid, Integer personType) {
        return JvtBoxUtil.deletePersonList(uuid, personType);
    }

    @Override
    public JvtBoxBean deleteJvtPerson(String uuid, Integer personType, String personId) {
        return JvtBoxUtil.deletePerson(uuid, personType, personId);
    }

    private String getJvtBoxUuid(JvtBoxBean jvtBoxBean) {
        JSONObject jsonObject = JSON.parseObject(JSON.toJSONString(jvtBoxBean.getData()));
        JSONObject deviceInfo = jsonObject.getJSONObject("DeviceInfo");
        if (deviceInfo == null) {
            log.warn("【巨龙】没有找到DeviceInfo");
            return null;
        }
        String uuid = deviceInfo.getString("DeviceUUID");
        if (StringUtil.isNullOrBlank(uuid)) {
            log.warn("【巨龙】没有找到DeviceUUID");
            return null;
        }
        return uuid;
    }

    @Override
    public JvtBoxBean registerJvtBox(JvtBoxBean jvtBoxBean) {
        String uuid = getJvtBoxUuid(jvtBoxBean);
        if (StringUtil.isNullOrBlank(uuid)) {
            return null;
        }

        JvtBoxBean result = new JvtBoxBean();
        result.setName("registerResponse");
        result.setTimeStamp(System.currentTimeMillis() / 1000);
        result.setCode(1);
        JSONObject resultData = new JSONObject();
        resultData.put("Session", uuid);
        resultData.put("ServerVersion", "1.1.0");
        result.setData(resultData);
        return result;
    }

    @Override
    public JvtBoxBean heartbeatJvtBox(JvtBoxBean jvtBoxBean) {
        String uuid = getJvtBoxUuid(jvtBoxBean);
        if (StringUtil.isNullOrBlank(uuid)) {
            return null;
        }

        JvtBoxBean result = new JvtBoxBean();
        result.setName("heartbeatResponse");
        result.setTimeStamp(System.currentTimeMillis() / 1000);
        result.setCode(1);
        result.setSession(uuid);

        //60秒上报一次
        log.info("【巨龙】更新redis心跳 UUID:{}", uuid);
        redisTemplateUtil.set(getHeartBeatKey(uuid), System.currentTimeMillis());

        startTaskThread();

        return result;
    }

    /**
     * 开始任务执行线程
     */
    private void startTaskThread() {
        //判断是否需要启动或重启线程池
        if (!checkNeedInitTaskThread()) {
            return;
        }

        log.info("【巨龙】初始化巨龙执行线程池");
        executorService = Executors.newSingleThreadScheduledExecutor();
        executorService.scheduleAtFixedRate(() -> {
            //记录上次执行任务时间
            mLastTaskTime = System.currentTimeMillis();
            JvtBoxBean task = getJvtTask();
            if (task == null) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return;
            }

            //执行前保存此次任务
            mLastTask = task;
            try {
                //标识盒子处于繁忙状态
                redisTemplateUtil.set(getStatusKey(task.getUuid()), "1", RedisKeyConstant.EXPIRATION_TIME_1);
                //执行任务
                JvtBoxBean result = JvtBoxUtil.execTask(task);

                //若p2p服务未返回
                if (result != null) {
                    //若p2p服务返回失败或图片过大
                    if (result.getCode() != 1) {
                        //将任务失败结果保存到door库device_log表
                        addToDeviceLogs(task, result);
                    }
                    //任务正常执行，清除上一次任务
                    mLastTask = null;
                }
            } catch (Exception e) {
                log.error("【巨龙】执行巨龙任务异常，{}", e.getMessage());
            } finally {
                Long remain = logRemain(task.getUuid());
                //任务全部处理完之后，删除集合里面的任务key，降低后面取到空任务的几率
                if(null != remain && remain <1){
                    redisTemplateUtil.sRemove(RedisKeyConstant.WKBB_BASIC_JVT_TASK_KEYS,getTaskKey(task.getUuid()));
                }
                //标识盒子处于空闲状态
                redisTemplateUtil.set(getStatusKey(task.getUuid()), "0", RedisKeyConstant.EXPIRATION_TIME_1);
                //若到此处mLastTask不为null，则上次任务执行出错（未得到服务器响应）
                if (mLastTask != null) {
                    addErrorTaskToQueue(mLastTask);
                }
            }
        }, 100, 100, TimeUnit.MILLISECONDS);
    }

    /**
     * 防止任务执行线程池死掉
     */
    private boolean checkNeedInitTaskThread() {
        if (executorService != null) {
            //30秒未刷新任务状态，重启线程池
            if (mLastTaskTime != 0 && System.currentTimeMillis() - mLastTaskTime > TASK_TIME_OUT) {
                log.warn("【巨龙】任务线程池阻塞,超时{}ms", System.currentTimeMillis() - mLastTaskTime);
                executorService.shutdownNow();

                if (mLastTask != null) {
                    addErrorTaskToQueue(mLastTask);
                }
            } else {
                return false;
            }
        }

        return true;
    }

    private void addErrorTaskToQueue(JvtBoxBean task) {
        //若是清空用户任务，则必须执行成功后，再执行剩下的任务
        if (JvtBoxUtil.ACTION_DELETE_PERSON_LIST.equals(task.getAction())) {
            redisTemplateUtil.rightPush(getTaskKey(task.getUuid()), task);
        } else {
            //执行异常后，任务重新加入队列
            task.clearPersonPhoto();
            redisTemplateUtil.leftPush(getTaskKey(task.getUuid()), task);
        }

        //将每个设备的任务队列 的key 放到 set里面，不再使用scan命令去模糊查询设备的任务队列key（性能很差）
        redisTemplateUtil.sAdd(RedisKeyConstant.WKBB_BASIC_JVT_TASK_KEYS,getTaskKey(task.getUuid()));

        //上次未执行完的任务
        log.warn("【巨龙】将上次未执行完的任务加入队列,{}", mLastTask);
        mLastTask = null;
    }

    private void addToDeviceLogs(JvtBoxBean task, JvtBoxBean result) {
        log.warn("【巨龙】加入日志");
        task.clearPersonPhoto();
        result.clearPersonPhoto();
        DoorDeviceLogsDto deviceLogsDto = new DoorDeviceLogsDto();
        DeviceBoxDto deviceBoxInfo = deviceBoxService.getDeviceBoxInfo(task.getUuid());
        if (deviceBoxInfo != null) {
            deviceLogsDto.setOrgId(deviceBoxInfo.getOrgId());
            deviceLogsDto.setOrgName(deviceBoxInfo.getOrgName());
        }
        deviceLogsDto.setDeviceCode(task.getUuid());
        deviceLogsDto.setLogPath(result.getCode() == JvtBoxUtil.CODE_ERROR_IMG_SIZE.intValue() ? result.getMessage() : JSON.toJSONString(result));
        deviceLogsDto.setComment(JSON.toJSONString(task));
        deviceLogsDto.setLevel(5);
        doorFeignService.addDeviceLogs(deviceLogsDto);
    }

    /**
     * 设备剩余任务数
     *
     * @param uuid 设备UUID
     */
    private Long logRemain(String uuid) {
        Long count = redisTemplateUtil.listCount(getTaskKey(uuid));
        log.info("【巨龙】盒子 UUID:{},剩余任务:{}个", uuid, count == null ? 0 : count);
        return count;
    }

    private JvtBoxBean getJvtTask() {
        Set<Object> taskKeys =redisTemplateUtil.sMembers(RedisKeyConstant.WKBB_BASIC_JVT_TASK_KEYS);
        for (Object key : taskKeys) {
            if(null == key){
                continue;
            }
            //通过redisKey获取uuid
            String uuid = getParamFromKey(key.toString(), "uuid");

            if (StringUtil.isNullOrBlank(uuid)) {
                continue;
            }

            // 如果设备不在线，不处理此任务
            if (!isJvtBoxAlive(uuid)) {
                continue;
            }

            //如果设备正在处理任务，找下一个设备
            if (isJvtBoxWorking(uuid)) {
                continue;
            }

            //从任务列表首端弹出一个任务
            JvtBoxBean task = (JvtBoxBean) redisTemplateUtil.rightPop(key.toString());
            if (task != null) {
                return task;
            }
        }
        return null;
    }

    private String getParamFromKey(String key, String param) {
        String[] strings = key.split(":");
        for (String string : strings) {
            if (string.contains(param)) {
                String[] split = string.split("_");
                if (split.length == 2) {
                    return split[1];
                }
            }
        }
        return null;
    }

    private boolean isJvtBoxWorking(String uuid) {
        return "1".equals(redisTemplateUtil.get(getStatusKey(uuid), false));
    }

    @Override
    public boolean isJvtBoxAlive(String uuid) {
        Long heartbeat = (Long) redisTemplateUtil.get(getHeartBeatKey(uuid), false);
        if (heartbeat == null) {
            return false;
        } else {
            //上次心跳redis离现在不超过3分钟就相当于设备在线
            return System.currentTimeMillis() - heartbeat <= 3 * 60 * 1000;
        }
    }

    /**
     * 获取心跳 RedisKey
     *
     * @param uuid 设备UUID
     */
    private String getHeartBeatKey(String uuid) {
        return RedisKeyConstant.WKBB_BASIC_JVT_HEARTBEAT + "uuid_" + uuid + ":";
    }

    /**
     * 获取任务 RedisKey
     *
     * @param uuid 设备UUID
     */
    @Override
    public String getTaskKey(String uuid) {
        return RedisKeyConstant.WKBB_BASIC_JVT_TASK + "uuid_" + uuid + ":";
    }

    /**
     * 获取状态 RedisKey
     *
     * @param uuid 设备UUID
     */
    private String getStatusKey(String uuid) {
        return RedisKeyConstant.WKBB_BASIC_JVT_STATUS + "uuid_" + uuid + ":";
    }

}
